﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Fortuitus.Core;
using System.Threading.Tasks;

using System.IO;
using Smrf.NodeXL.Algorithms;
using Smrf.NodeXL.Core;
using Microsoft.NodeXL.Adapters;

namespace ConanLib
{
    public class Pipeline
    {
        public void Run(RunConfiguration config)
        {
            try
            {
                string configPath = Path.Combine(config.OutputDir, "~runconfig.txt");
                WriteConfig(config, configPath);

                double maxPCC;
                IGraph allNetworks;
                List<IGraph> graphs = new List<IGraph>();
                IOrderedList<IVector> features = null;
                ColorMapper _colorMapper = new ColorMapper();
                _colorMapper.Init();

                //if (!config.PerformSideLoad)
                //{
                IEnumerable<ClusterItem> clusterItems;
                Dictionary<string, int> index;
                if (config.PerformSideLoad)
                {
                    clusterItems = LoadCorrelationMatrix(config.SideLoadFile, config.MinCorrelationCoefficientFilter, out index);
                }
                else
                {
                    //load and transform data
                    features = ReadData(config.InputFile, config.MinFoldChangeFilter, config.DoLogTransformation, config.Skip, config.Take, _colorMapper);

                    //calclulate PCC 


                    IRelationshipCalculator calc = null;
                    if (config.CCType == CorrelationCoefficient.PEARSON)
                        calc = new PearsonCalculator();
                    else if (config.CCType == CorrelationCoefficient.SPEARMAN)
                        calc = new SpearmanCalculator();


                    string corelationMatrixFile = Path.Combine(config.OutputDir, "~correlationMatrix.txt");
                    if (File.Exists(corelationMatrixFile))
                        File.Delete(corelationMatrixFile);
                    StreamWriter sw = new StreamWriter(File.OpenWrite(corelationMatrixFile));
                    clusterItems = NetworkHelper.CalculateClusterParallel(features, calc, sw, config.MinCorrelationCoefficientFilter, config.MinVectorLengthFilter);
                    if (clusterItems.Count() == 0)
                    {
                        Console.WriteLine("no correlations found");
                        return;
                    }



                }
                // SaveCorrelationMatrix(clusterItems, Path.Combine(config.OutputDir, "~correlationMatrix.txt"));
                maxPCC = clusterItems.Max(x => Math.Abs(x.RelationshipValue));

                Console.WriteLine("initialize cluster...");
                List<Cluster> clusters = new List<Cluster>();
                foreach (var item in clusterItems) //create cluster with just one item
                {
                    Cluster c = new Cluster();
                    c.Items.Add(item);
                    clusters.Add(c);

                }
                //just create one cluster that contains all
                Cluster all = new Cluster();
                all.Id = 0;
                foreach (var item in clusters)
                {
                    all.Join(item);
                }



                //messing around here

                //clusters = new List<Cluster>();
                //clusters.Add(all);


                clusters = NetworkHelper.ClusterIndependentSubNetworks(clusters);
                List<Cluster> filterOut = new List<Cluster>();
                foreach (var cluster in clusters)
                {
                    IGraph graph = NetworkHelper.LoadGraphFromCluster(cluster, _colorMapper, config.MinCorrelationCoefficientFilter, maxPCC);
                    if (config.MinVertexOutputFilter <= 1 || graph.Vertices.Count >= config.MinVertexOutputFilter)
                    {
                        graphs.Add(graph);
                    }
                    else
                    {
                        filterOut.Add(cluster);
                    }
                }

                clusters.RemoveAll(x => filterOut.Contains(x));

                if (config.Layout == LayoutAlgorithm.SUGI || config.Layout == LayoutAlgorithm.POLAR || config.Layout == LayoutAlgorithm.HKFM)
                {
                    //workaround for layouts that do not work with grouping and independent networks
                    allNetworks = NetworkHelper.LoadGraphFromClusters(clusters, _colorMapper, config.MinCorrelationCoefficientFilter, maxPCC, false);
                    allNetworks.SetValue("ClusterId", 0);
                }
                else
                {
                    allNetworks = NetworkHelper.LoadGraphFromClusters(clusters, _colorMapper, config.MinCorrelationCoefficientFilter, maxPCC, true);
                    allNetworks.SetValue("ClusterId", 0);
                }
                //}
                //else
                //{
                //    //load network from file
                //    MyGraphAdapter adapter = new MyGraphAdapter();
                //    adapter.ColorMapper = _colorMapper;

                //    allNetworks = adapter.LoadGraphFromFile(config.SideLoadFile);
                //    allNetworks.SetValue("ClusterId", 0);
                //    maxPCC = allNetworks.Edges.Max(x => Math.Abs((double)x.GetValue("Weight")));
                //    graphs.Add(allNetworks);
                //}




                List<IGraph> subGraphs = new List<IGraph>();
                if (config.DoSubNetworks) //if subnetwork clustering is enabled then lets do it now to get the droup information for the layout
                {
                    subGraphs = NetworkHelper.ClusterSubNetworks(graphs, _colorMapper, config.MinCorrelationCoefficientFilter, maxPCC);
                }

                //add the allnetwork graph to all the other networks for the output
                // if (!config.PerformSideLoad)
                graphs.Insert(0, allNetworks);
                //Task t = new Task(() =>
                //  {
                WriteOutput(config.OutputDir, config.MinVertexOutputFilter, graphs, features, config.Layout, config.GenerateGraphics, config.GraphicsDPI);
                //  });
                //t.Start();

                if (config.DoSubNetworks && config.DoOutputSubNetworks)
                {
                    //graphs.Remove(allNetworks);
                    //    Task t2 = new Task(() =>
                    //{
                    WriteOutput2(config.OutputDir, config.MinVertexOutputFilter, subGraphs, features, config.Layout, config.GenerateGraphics, config.GraphicsDPI);
                    //});
                    //    t2.Start();
                }
                //t.Wait();
            }
            catch (NotImplementedException ex)
            {
                OutputException(ex);
            }
            finally
            {
                Console.WriteLine("...press enter to quit");
                Console.ReadLine();
            }
        }


        public static IEnumerable<ClusterItem> LoadCorrelationMatrix(string path, double minCorrelation, out Dictionary<string, int> nameIndex)
        {
            StreamReader sr = new StreamReader(File.OpenRead(path));
            int currentId = 0;
            int lineNumber = 1;
            List<ClusterItem> ret = new List<ClusterItem>();
            nameIndex = new Dictionary<string, int>();
            while (sr.Peek() > 0)
            {
                string line = sr.ReadLine();
                string[] array = line.Split("\t".ToCharArray());
                if (array.Length < 3)
                {
                    sr.Close();
                    OutputException(new FormatException("Invalid format. Expected format is Name1 tab Name2 tab Correlation tab Type1 tab Type2 tab Description1 tab Description2"));
                    return null;
                }
                string name1 = array[0].Trim();
                string name2 = array[1].Trim();
                if (!nameIndex.ContainsKey(name1))
                {
                    currentId++;
                    nameIndex.Add(name1, currentId);
                }
                if (!nameIndex.ContainsKey(name2))
                {
                    currentId++;
                    nameIndex.Add(name2, currentId);
                }
                double? relVal = default(double?);
                try
                {
                    relVal = double.Parse(array[2]);
                }
                catch (Exception ex)
                {
                    //  sr.Close();
                    OutputException(ex);
                    ConsoleColor temp = Console.ForegroundColor;
                    Console.ForegroundColor = ConsoleColor.Red;
                    Console.WriteLine("++++++++++++ Data in line " + lineNumber + " skipped.+++++++++++++++++++");
                    Console.ForegroundColor = temp;
                }
                if (relVal.HasValue)
                {
                    EditableVector v1 = new EditableVector(new List<double>());
                    v1.SetValue<int>("Id", nameIndex[name1]);
                    v1.SetValue<string>("Name", name1);

                    EditableVector v2 = new EditableVector(new List<double>());
                    v2.SetValue<int>("Id", nameIndex[name2]);
                    v2.SetValue<string>("Name", name2);
                    ClusterItem ci = new ClusterItem(v1, v2, relVal.Value);

                    if (array.Length > 3 && !string.IsNullOrEmpty(array[3]))
                        v1.SetValue<string>("Type", array[3]);
                    else
                        v1.SetValue<string>("Type", "N/A");
                    if (array.Length > 4 && !string.IsNullOrEmpty(array[4]))
                        v2.SetValue<string>("Type", array[4]);
                    else
                        v2.SetValue<string>("Type", "N/A");

                    if (array.Length > 5)
                        v1.SetValue<string>("Description", array[5]);
                    else
                        v1.SetValue<string>("Description", "");
                    if (array.Length > 6)
                        v2.SetValue<string>("Description", array[6]);
                    else
                        v2.SetValue<string>("Description", "");
                    if (Math.Abs(relVal.Value) >= minCorrelation)
                        ret.Add(ci);
                }
                lineNumber++;
            }

            sr.Close();
            return ret;
        }

        private static IOrderedList<IVector> ReadData(string filePath, double foldChange, bool doLog2transfomr, int skip, int take, ColorMapper colorMapping)
        {
            IOrderedList<EditableVector> features = (IOrderedList<EditableVector>)NetworkHelper.ReadData(filePath, colorMapping, skip, take);
            int totalFeatureCount = features.Count();

            if (foldChange > 1)
                features = new OrderedList<EditableVector>((IOrderedList<EditableVector>)NetworkHelper.FilterOut(features, (x) => { return Statistics.FoldChange(x.Min(), x.Max()) >= foldChange; }));

            int filteredFeatureCount = features.Count();

            Console.WriteLine("{0} selected from {1} based on {2} foldchange filter", filteredFeatureCount, totalFeatureCount, foldChange);

            if (doLog2transfomr)
            {
                Console.WriteLine("applying Log2 transformation...");
                Parallel.ForEach(features, (feature, loopstate, c) =>
                {
                    feature.Apply(x => Math.Log(x, 2));
                });
            }
            return features;
        }

        private static void WriteOutput(string outDir, int outfilter, List<IGraph> graphs, IEnumerable<IVector> features, LayoutAlgorithm layout, bool outputGraphics = false, int dpi = 96)
        {
            if (!Directory.Exists(outDir))
            {
                Directory.CreateDirectory(outDir);
            }


            List<string> _graphFilesNew = new List<string>();
            MyGraphAdapter graphAdapter = new MyGraphAdapter();
            VertexDegreeCalculator vdc = new VertexDegreeCalculator();

            //  List<string> files = new List<string>();

            // var ids = graphs.Select(x => (int?)x.GetValue("ClusterId"));
            //Console.WriteLine("begin output for graphs:");
            //foreach (var item in ids)
            //{
            //    Console.WriteLine(item);
            //}

            Parallel.ForEach<IGraph>(graphs, graph =>
            // foreach (var graph in graphs)
            {

                if (graph.Vertices.Count >= outfilter)
                {

                    int? clusterId = (int?)graph.GetValue("ClusterId");
                    clusterId = clusterId.HasValue ? clusterId.Value : 0;
                    var degrees = vdc.CalculateGraphMetrics(graph);
                    double avgDegree = degrees.Values.Average(x => (double)x.Degree);
                    double perfectDegree = graph.Vertices.Count() - 1;

                    bool isMixed = graph.Vertices.Select(x => x.GetValue("Type").ToString()).Distinct().Count() > 1;
                    string mixedIndicator = "";
                    if (isMixed)
                        mixedIndicator = "_TxT";
                    string degreePrefix = string.Format("ADR{0}", Math.Round(avgDegree / perfectDegree, 1));
                    string fileName = string.Format(Path.Combine(outDir, "network_{0}_C{1}-{2}{3}.txt"), clusterId.Value.ToString("0000"), graph.Vertices.Count, degreePrefix, mixedIndicator);
                    if (clusterId == 0)
                        fileName = string.Format(Path.Combine(outDir, "all_networks_C{0}.txt"), graph.Vertices.Count, degreePrefix);
                    FileInfo currentFile = new FileInfo(fileName);

                    //if (files.Contains(fileName))
                    //    throw new Exception("fuck: " + fileName);
                    //files.Add(fileName);


                    graphAdapter.SaveGraph(graph, currentFile.FullName);

                    fileName = string.Format(Path.Combine(outDir, "network_{0}_C{1}-{2}{3}_member.txt"), clusterId.Value.ToString("0000"), graph.Vertices.Count, degreePrefix, mixedIndicator);
                    if (clusterId == 0)
                        fileName = string.Format(Path.Combine(outDir, "all_networks_C{1}_member.txt"), clusterId.Value.ToString("0000"), graph.Vertices.Count, degreePrefix, mixedIndicator);
                    WriteMemberFile(graph, fileName);
                    //if (files.Contains(fileName))
                    //    throw new Exception("fuck");
                    //files.Add(fileName);

                    if (outputGraphics)
                    {
                        try
                        {
                            if (features != null)
                            {
                                List<IVector> f = NetworkHelper.GetFeaturesFromGraph(graph, features);

                                NetworkHelper.GenerateHeatMap(f, currentFile.FullName.Replace(currentFile.Extension, "-.png"));
                            }
                        }
                        catch (Exception ex)
                        {
                            OutputException(ex);
                        }
                        try
                        {

                            NetworkHelper.GenerateGraphImage(graph, currentFile.FullName.Replace(currentFile.Extension, ".png"), dpi, layout);
                        }
                        catch (Exception ex)
                        {
                            OutputException(ex);
                        }

                    }
                }

            }
           );

        }

        private static void WriteOutput2(string outDir, int outfilter, IEnumerable<IGraph> graphs, IEnumerable<IVector> features, LayoutAlgorithm layout, bool outputGraphics = false, int dpi = 96)
        {
            List<string> _graphFilesNew = new List<string>();
            MyGraphAdapter graphAdapter = new MyGraphAdapter();
            VertexDegreeCalculator vdc = new VertexDegreeCalculator();
            //  List<string> files = new List<string>();
            Parallel.ForEach<IGraph>(graphs, graph =>
            //  foreach (var graph in graphs)
            {

                if (graph.Vertices.Count >= outfilter)
                {
                    //List<IVector> f = NetworkHelper.GetFeaturesFromGraph(graph, features);
                    int? clusterId = (int?)graph.GetValue("ParentClusterId");
                    var degrees = vdc.CalculateGraphMetrics(graph);
                    double avgDegree = degrees.Values.Average(x => (double)x.Degree);
                    double perfectDegree = graph.Vertices.Count() - 1;
                    string degreePrefix = string.Format("ADR{0}", Math.Round(avgDegree / perfectDegree, 1));
                    bool isMixed = graph.Vertices.Select(x => x.GetValue("Type").ToString()).Distinct().Count() > 1;
                    string mixedIndicator = "";
                    if (isMixed)
                        mixedIndicator = "_TxT";

                    string fileName = string.Format(Path.Combine(outDir, "network_{0}_Com{2}_C{1}-{3}{4}.txt"), clusterId.Value.ToString("0000"), graph.Vertices.Count, graph.ID, degreePrefix, mixedIndicator);
                    //if (files.Contains(fileName))
                    //    throw new Exception("fuck");
                    //files.Add(fileName);
                    FileInfo currentFile = new FileInfo(fileName);


                    graphAdapter.SaveGraph(graph, currentFile.FullName);
                    fileName = string.Format(Path.Combine(outDir, "network_{0}_Com{2}_C{1}-{3}{4}_member.txt"), clusterId.Value.ToString("0000"), graph.Vertices.Count, graph.ID, degreePrefix, mixedIndicator);
                    //if (files.Contains(fileName))
                    //    throw new Exception("fuck");
                    //files.Add(fileName);
                    WriteMemberFile(graph, fileName);

                    if (outputGraphics)
                    {
                        try
                        {
                            if (features != null)
                            {
                                List<IVector> f = NetworkHelper.GetFeaturesFromGraph(graph, features);
                                NetworkHelper.GenerateHeatMap(f, currentFile.FullName.Replace(currentFile.Extension, "-.png"));
                            }
                        }
                        catch (Exception ex)
                        {
                            OutputException(ex);
                        }
                        try
                        {
                            NetworkHelper.GenerateGraphImage(graph, currentFile.FullName.Replace(currentFile.Extension, ".png"), dpi, layout);
                        }
                        catch (Exception ex)
                        {
                            OutputException(ex);
                        }
                    }
                }

            }
            );



        }

        private static void WriteMemberFileOld(List<IVector> f, string filePath)
        {
            StreamWriter sw = new StreamWriter(File.Create(filePath));
            int columns = 0;
            foreach (var item in f)
            {
                columns = Math.Max(item.GetValue<string>("Description").Split("$$".ToCharArray()).Length, columns);
            }
            foreach (var item in f)
            {
                StringBuilder sb = new StringBuilder();
                var splittedDesc = item.GetValue<string>("Description").Split("$".ToCharArray());
                sb.AppendFormat("{0}\t", item.GetValue<string>("Name"));
                for (int i = 0; i < columns; i++)
                {
                    if (i < splittedDesc.Length)
                        sb.AppendFormat("{0}", splittedDesc[i].Replace("\"", ""));
                    else
                        sb.AppendFormat("{0}", "");
                    if (i < columns - 1)
                        sb.Append("\t");
                }
                sw.WriteLine(sb.ToString());
                // sw.Write("{0}\t{1}", item.GetValue<string>("Name"), item.GetValue<string>("Description").Replace("$$", "\t"));
            }
            sw.Flush();
            sw.Close();

        }

        private static void WriteMemberFile(IGraph graph, string filePath)
        {
            StreamWriter sw = new StreamWriter(File.Create(filePath));
            int columns = 0;
            foreach (var item in graph.Vertices)
            {
                columns = Math.Max(item.GetValue("Description").ToString().Split("$$".ToCharArray()).Length, columns);
            }
            sw.Write("Name\tClosenessCentrality\tBetweenessCentrality\tBetweenessCentralityNormalized\tEigenvectorCentrality");
            for (int i = 0; i < columns; i++)
            {
                sw.Write("\tDescription{0}", i + 1);
            }
            sw.WriteLine();

            BrandesFastCentralityCalculator brandes = new BrandesFastCentralityCalculator();
            var brandesmetrics = brandes.CalculateGraphMetrics(graph);
            EigenvectorCentralityCalculator eigen = new EigenvectorCentralityCalculator();
            var eigenmetrics = eigen.CalculateGraphMetrics(graph);

            double normfactor = (graph.Vertices.Count - 1) * (graph.Vertices.Count - 2) / 2;

            //OverallMetricCalculator omc = new OverallMetricCalculator();
            //var bla = omc.CalculateGraphMetrics(graph);

            foreach (var item in graph.Vertices)
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendFormat("{0}", item.Name);
                if (brandesmetrics.ContainsKey(item.ID))
                {
                    sb.AppendFormat("\t{0}\t{1}\t{2}", brandesmetrics[item.ID].ClosenessCentrality, brandesmetrics[item.ID].BetweennessCentrality, brandesmetrics[item.ID].BetweennessCentrality / normfactor);
                }
                else
                {
                    sb.AppendFormat("\t{0}\t{1}\t{1}", double.NaN, double.NaN, double.NaN);
                }
                if (eigenmetrics.ContainsKey(item.ID))
                {
                    sb.AppendFormat("\t{0}", eigenmetrics[item.ID]);
                }
                else
                {
                    sb.AppendFormat("\t{0}", double.NaN);
                }

                var splittedDesc = item.GetValue("Description").ToString().Split("$".ToCharArray());

                for (int i = 0; i < columns; i++)
                {
                    if (i < splittedDesc.Length)
                        sb.AppendFormat("\t{0}", splittedDesc[i].Replace("\"", ""));
                    else
                        sb.AppendFormat("\t{0}", "");
                    if (i < columns - 1)
                        sb.Append("\t");
                }

                sw.WriteLine(sb.ToString());
                // sw.Write("{0}\t{1}", item.GetValue<string>("Name"), item.GetValue<string>("Description").Replace("$$", "\t"));
            }
            sw.Flush();
            sw.Close();

        }

        private static void WriteConfig(RunConfiguration config, string filePath)
        {
            if (!Directory.Exists(config.OutputDir))
            {
                Directory.CreateDirectory(config.OutputDir);
            }

            if (File.Exists(filePath))
                File.Delete(filePath);

            StreamWriter sw = File.CreateText(filePath);
            sw.WriteLine("InputFile........................: {0}", config.PerformSideLoad ? "N/A" : config.InputFile);
            sw.WriteLine("SideloadFile.....................: {0}", !config.PerformSideLoad ? "N/A" : config.SideLoadFile);
            sw.WriteLine("DoSideLoad.......................: {0}", config.PerformSideLoad);
            sw.WriteLine("OutputDir........................: {0}", config.OutputDir);
            sw.WriteLine("MinFoldChangeFilter..............: {0}", config.PerformSideLoad ? "N/A" : config.MinFoldChangeFilter.ToString());
            sw.WriteLine("DoLogTransformation..............: {0}", config.PerformSideLoad ? "N/A" : config.DoLogTransformation.ToString());
            sw.WriteLine("CorrelationCoefficient...........: {0}", config.PerformSideLoad ? "N/A" : config.CCType.ToString());
            sw.WriteLine("DoSubNetworks....................: {0}", config.DoSubNetworks);
            sw.WriteLine("DoOutputSubNetworks..............: {0}", config.DoOutputSubNetworks);
            sw.WriteLine("MinCorrelationCoefficientFilter..: {0}", config.MinCorrelationCoefficientFilter);
            sw.WriteLine("MinVectorLengthFilter............: {0}", config.PerformSideLoad ? "N/A" : config.MinVectorLengthFilter.ToString());
            sw.WriteLine("MinVertexOutputFilter............: {0}", config.MinVertexOutputFilter);
            sw.WriteLine("SkipFirst X Lines................: {0}", config.PerformSideLoad ? "N/A" : config.Skip.ToString());
            sw.WriteLine("Take X Lines.....................: {0}", config.PerformSideLoad ? "N/A" : config.Take.ToString());
            sw.WriteLine("GenerateGraphics.................: {0}", config.GenerateGraphics);
            sw.WriteLine("GraphicsDPI......................: {0}", config.GraphicsDPI);
            sw.WriteLine("Layout...........................: {0}", config.Layout);
            sw.Flush();
            sw.Close();
        }

        private static void OutputException(Exception ex)
        {
            ConsoleColor temp = Console.ForegroundColor;
            Console.ForegroundColor = ConsoleColor.Red;

            Console.WriteLine(ex.Message);
            Console.WriteLine(ex.StackTrace);
            if (ex.InnerException != null)
            {

                Console.WriteLine(ex.InnerException.Message);
                Console.WriteLine(ex.InnerException.StackTrace);
                if (ex.InnerException.InnerException != null)
                {
                    Console.WriteLine(ex.InnerException.InnerException.Message);
                    Console.WriteLine(ex.InnerException.InnerException.StackTrace);
                }
            }
            if (ex is AggregateException)
            {
                foreach (Exception item in (ex as AggregateException).InnerExceptions)
                {

                    Console.WriteLine(item.Message);
                    Console.WriteLine(item.StackTrace);
                    if (item.InnerException != null)
                    {

                        Console.WriteLine(item.InnerException.Message);
                        Console.WriteLine(item.InnerException.StackTrace);
                    }
                }

            }
            Console.ForegroundColor = temp;
        }
    }

}



